package cn.touna.jss.modules.third.geo.util;

import cn.touna.jss.common.util.RSAUtils;
import cn.touna.jss.common.util.Secret;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.security.KeyPair;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;


/**
 * 数据请求client
 * wushujia
 */
@Component
public class Client {

    private static final Logger LOGGER = LoggerFactory.getLogger(Client.class);
    private String server; // http://yz.geotmt.com、https://yz.geotmt.com
    private int encrypted; // 是否加密传输
    private String encryptionType; // AES(秘钥长度不固定)、DES(秘钥长度8)、DESede(秘钥长度24)
    private String encryptionKey; // 加密类型和加密秘钥向GEO索取(如果是获取数据的时候传的是RSA那么这里自己定义即可)
    private String username; // 账户向GEO申请开通
    private String password;// 账户密码
    private String uno;//用户编号
    private String etype;
    private int dsign; // 是否进行数字签名 1是0否

    private int httpConnectTimeout;

    private int httpReadTimeout;

    public String getEtype() {
        return etype;
    }

    public void setEtype(String etype) {
        this.etype = etype;
    }

    public int getEncrypted() {
        return encrypted;
    }

    public void setEncrypted(int encrypted) {
        this.encrypted = encrypted;
    }

    public String getServer() {
        return server;
    }

    public String getEncryptionType() {
        return encryptionType;
    }

    public String getEncryptionKey() {
        return encryptionKey;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public String getUno() {
        return uno;
    }

    public void setServer(String server) {
        this.server = server;
    }

    public void setEncryptionType(String encryptionType) {
        this.encryptionType = encryptionType;
    }

    public void setEncryptionKey(String encryptionKey) {
        this.encryptionKey = encryptionKey;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setUno(String uno) {
        this.uno = uno;
    }

    public int getDsign() {
        return dsign;
    }

    public void setDsign(int dsign) {
        this.dsign = dsign;
    }

    public int getHttpConnectTimeout() {
        return httpConnectTimeout;
    }

    public void setHttpConnectTimeout(int httpConnectTimeout) {
        this.httpConnectTimeout = httpConnectTimeout;
    }

    public int getHttpReadTimeout() {
        return httpReadTimeout;
    }

    public void setHttpReadTimeout(int httpReadTimeout) {
        this.httpReadTimeout = httpReadTimeout;
    }


    private final Map<String, String> tokenIdMap = Token.getInstance().getTokenIdMap();
    private final Map<String, Long> getTokenTimeMap = Token.getInstance().getGetTokenTimeMap();
    private final Map<String, String> digitalSignatureKeyMap = Token.getInstance().getDigitalSignatureKeyMap();

    private static final long tokenCycle = 86400000l;
    private static final long tokenCyc = 35000l;  // 避免高并发时刚好token过期造成同时多个线程一起申请新token，如果在此时间内有过更新那么直接返回内存里面的token

    /**
     * 该方法可以通过用户名密码直接调用数据接口，且token过期会自动重新获取
     *
     * @param path
     * @param params
     * @param headers
     * @return
     */
    public String getData(String path, Map<String, String> params) {
        Map<String, String> headers = new HashMap<String, String>();
        TreeMap<String, String> paraMap = new TreeMap<String, String>();
        String tokenId = getToken(true);  // 等计费功能上线后第三个参数才能填RSA不然都只能是空
        StringBuilder sb = new StringBuilder();
        if (params != null) {
            Set<String> set = params.keySet();
            for (String k : set) {
                String value = params.get(k);
                if (dsign == 1) {
                    paraMap.put(k, value);
                }
                value = Secret.encrypt(encryptionType, value, encryptionKey); // 加密
                sb.append(k).append("=").append(value).append("&");
            }
        }
        if (dsign == 1) {
            paraMap.put("tokenId", tokenId);
            String digitalSignature = DigitalSignature.clientDigitalSignature(paraMap, null, "", digitalSignatureKeyMap.get(username));
            headers.put("digitalSignature", digitalSignature);
        }
        String newParams = sb.toString() + "&tokenId=" + tokenId;
        String data = getDataByTokenId(path, newParams, headers);
        Map<String, Object> map = JSON.parseObject(data, Map.class);
        String code = map.get("code") + "";
        if ("-100".equals(code) || "-200".equals(code) || "-300".equals(code)) {
            LOGGER.info("tokenId无效重新获取tokenId-------------");
            tokenId = getToken(false);  // 等计费功能上线后第三个参数才能填RSA不然都只能是空
            if (dsign == 1) {
                paraMap.put("tokenId", tokenId);
                String digitalSignature = DigitalSignature.clientDigitalSignature(paraMap, null, "", digitalSignatureKeyMap.get(username));
                headers.put("digitalSignature", digitalSignature);
            }
            newParams = sb.toString() + "&tokenId=" + tokenId;
            data = getDataByTokenId(path, newParams, headers);
        }
        return data;
    }

    /**
     * @param username
     * @param password
     * @param etype
     * @param first
     * @return
     */
    public String getToken(boolean first) {
        if (first) {
            if (getNewToken()) {
                // 走网络获取token需要同步
                synchronized (username.intern()) {
                    return getToken();
                }
            } else {
                return tokenIdMap.get(username);
            }
        } else {
            synchronized (username.intern()) {
                Long tokenTime = getTokenTimeMap.get(username);
                long getTokenTime = tokenTime == null ? 0l : tokenTime;
                if (System.currentTimeMillis() - getTokenTime >= tokenCyc) {
                    // 避免高并发
                    tokenIdMap.put(username, ""); // 使token失效
                }
                String tokenId = getToken();
                return tokenId;
            }
        }
    }

    /**
     * 获取数据访问权限,该接口24小时请求一次即可！
     *
     * @param username
     * @param password
     * @param etype    加密类型和秘钥用什么方式进行加密
     * @return token
     */
    public String getToken() {
        if (getNewToken()) {
            String path = server + "/civp/getview/api/o/login";
            // 加密用户名密码
            String eUsername = Secret.encrypt(encryptionType, username, encryptionKey);
            String ePassword = Secret.encrypt(encryptionType, password, encryptionKey);
            String eDsign = Secret.encrypt(encryptionType, dsign + "", encryptionKey);
            String params = "";
            if ("RSA".equalsIgnoreCase(etype)) {
                KeyPair keyPair = RSAUtils.getKeyPair();
                String paraEncryptionType = RSAUtils.encrypt(keyPair.getPublic(), encryptionType);
                String paraEncryptionKey = RSAUtils.encrypt(keyPair.getPublic(), encryptionKey);
                params = "username=" + eUsername + "&password=" + ePassword + "&etype=" + etype + "&encryptionType=" + paraEncryptionType + "&encryptionKey=" + paraEncryptionKey + "&encrypted=" + encrypted + "&dsign=" + eDsign;
            } else {
                params = "username=" + eUsername + "&password=" + ePassword + "&uno=" + uno + "&encrypted=" + encrypted + "&dsign=" + eDsign;
            }
            String reqencoding = "UTF-8";
            String respencoding = "UTF-8";
            String requestMethod = "POST";
            Map<String, String> headerMap = null;
            String rs = HttpClientGeo.getRs(path, params, reqencoding, respencoding, requestMethod, httpConnectTimeout, httpReadTimeout, headerMap);
            LOGGER.info("------------------" + rs);
            // 解密返回值
            if (rs.startsWith("{")) {
                LOGGER.info("未加密:" + rs);
            } else {
                rs = Secret.decrypt(encryptionType, rs, encryptionKey);
                LOGGER.info("解密:" + rs);
            }
            Map<String, Object> map = JSON.parseObject(rs, Map.class);
            if ("200".equals(map.get("code") + "")) {
                long getTokenTime = System.currentTimeMillis();
                String tokenId = map.get("tokenId") + "";
                tokenIdMap.put(username, tokenId);
                getTokenTimeMap.put(username, getTokenTime);
                Map data = (Map) map.get("data");
                if (data != null) {
                    String digitalSignatureKey = (String) data.get("digitalSignatureKey");
                    if (digitalSignatureKey != null) {
                        digitalSignatureKeyMap.put(username, digitalSignatureKey);
                    }
                }
                return tokenId;
            } else {
                LOGGER.info("登录失败!code=" + map.get("code"));
                return "";
            }
        } else {
            return tokenIdMap.get(username);
        }
    }

    /**
     * 获取数据
     *
     * @param path
     * @param params
     * @param headers
     * @return
     */
    public String getDataByTokenId(String path, String params, Map<String, String> headers) {
        String reqencoding = "UTF-8";
        String respencoding = "UTF-8";
        String requestMethod = "POST";
        String rs = HttpClientGeo.getRs(path, params, reqencoding, respencoding, requestMethod, httpConnectTimeout, httpReadTimeout, headers);
        LOGGER.info("------------------" + rs);
        // 解密返回值
        if (rs.startsWith("{")) {
            LOGGER.info("未加密:" + rs);
        } else {
            rs = Secret.decrypt(encryptionType, rs, encryptionKey);
            LOGGER.info("解密:" + rs);
        }
        Map<String, Object> map = JSON.parseObject(rs, Map.class);
        if (!"200".equals(map.get("code") + "")) {
            System.out.println("数据请求失败!code=" + map.get("code"));
        }
        return rs;
    }

    /**
     * 登出接口
     *
     * @return
     */
    public String loginOut() {
        String tokenId = tokenIdMap.get(username);
        if (tokenId != null && !"".equals(tokenId)) {
            String digitalSignatureKey = digitalSignatureKeyMap.get(username);
            Map<String, String> headers = new HashMap<String, String>();
            if (digitalSignatureKey != null && !"".equals(digitalSignatureKey)) {
                TreeMap<String, String> paraMap = new TreeMap<String, String>();
                paraMap.put("tokenId", tokenId);
                String digitalSignature = DigitalSignature.clientDigitalSignature(paraMap, null, "", digitalSignatureKeyMap.get(username));
                headers.put("digitalSignature", digitalSignature);
            }
            return loginOut(tokenId, headers);
        }
        LOGGER.info("用户未登录");
        return "";
    }

    /**
     * 登出(该token权限将被回收),该接口一般不需要调用
     *
     * @param tokenId
     * @return
     */
    public String loginOut(String tokenId, Map<String, String> headerMap) {
        String path = server + "/civp/getview/api/u/logout";
        String params = "tokenId=" + tokenId;
        String reqencoding = "UTF-8";
        String respencoding = "UTF-8";
        String requestMethod = "POST";
        String rs = HttpClientGeo.getRs(path, params, reqencoding, respencoding, requestMethod, httpConnectTimeout, httpReadTimeout, headerMap);
        // 解密返回值
        if (rs.startsWith("{")) {
            LOGGER.info("未加密:" + rs);
        } else {
            rs = Secret.decrypt(encryptionType, rs, encryptionKey);
            LOGGER.info("解密:" + rs);
        }
        Map<String, Object> map = JSON.parseObject(rs, Map.class);
        if ("200".equals(map.get("code") + "")) {
            System.out.println("退出成功");
            tokenIdMap.remove(username);
            getTokenTimeMap.remove(username);
            digitalSignatureKeyMap.remove(username);
        } else {
            LOGGER.info("退出失败");
        }
        return rs;
    }

    /**
     * 判定token是否还有效
     *
     * @param username
     * @return
     */
    public boolean getNewToken() {
        Long tokenTime = getTokenTimeMap.get(username);
        long getTokenTime = tokenTime == null ? 0l : tokenTime;
        return tokenIdMap.get(username) == null || "".equals(tokenIdMap.get(username)) || System.currentTimeMillis() - getTokenTime >= tokenCycle;
    }

    public String rpad(String str, int strLength, char chr) {
        int strLen = str.length();
        if (strLen < strLength) {
            StringBuffer sb = new StringBuffer(str);
            while (strLen < strLength) {
                sb.append(chr); // 右补0
                strLen = sb.length();
            }
            str = sb.toString();
        }
        return str;
    }

    public String rpad(String str, int strLength) {
        return rpad(str, strLength, '0');
    }
}
